"""
主命令行界面

实现ES备份工具的主要命令行接口。
"""

import click
import sys
from pathlib import Path
from typing import Optional

from ..utils.logger import setup_logging, get_module_logger
from ..config.settings import get_settings, reload_settings
from ..utils.exceptions import ESArchiveError

logger = get_module_logger(__name__)


class CLIContext:
    """CLI上下文对象"""

    def __init__(self):
        self.config = None
        self.verbose = False
        self.quiet = False
        self.debug = False
        self.config_file = None


pass_context = click.make_pass_decorator(CLIContext, ensure=True)


@click.group()
@click.option(
    '--config', '-c',
    type=click.Path(exists=True, path_type=Path),
    help='配置文件路径 (支持 YAML/JSON 格式)'
)
@click.option(
    '--verbose', '-v',
    is_flag=True,
    help='启用详细输出 (显示调试信息)'
)
@click.option(
    '--quiet', '-q',
    is_flag=True,
    help='静默模式 (仅显示错误信息)'
)
@click.option(
    '--log-level',
    type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR']),
    default='INFO',
    show_default=True,
    help='设置日志级别'
)
@click.option(
    '--debug',
    is_flag=True,
    help='启用调试模式 (显示所有ES操作的等效curl命令)'
)
@pass_context
def cli(ctx: CLIContext, config: Optional[Path], verbose: bool, quiet: bool, log_level: str, debug: bool):
    """ES Archive - 企业级Elasticsearch备份恢复工具

    \b
    功能特性:
      • 支持完整备份、增量备份、时间范围备份
      • 多种压缩算法 (gzip, lz4) 节省存储空间
      • 并行处理提升备份/恢复性能
      • 数据完整性验证确保可靠性
      • 灵活的时间字段检测和处理
      • 支持大规模数据的分片处理

    \b
    快速开始:
      # 查看集群中的备份
      es-archive list-backups

      # 备份索引
      es-archive backup my-index /backup/path

      # 恢复数据
      es-archive restore backup_id target-index

    \b
    环境变量配置:
      ES_HOST      Elasticsearch服务器地址 (默认: http://localhost:9200)
      ES_USERNAME  认证用户名
      ES_PASSWORD  认证密码
      ES_API_KEY   API密钥 (替代用户名密码)

    \b
    配置文件示例 (es-config.yaml):
      elasticsearch:
        hosts: ["http://es-server:9200"]
        username: "elastic"
        password: "password"
      backup:
        batch_size: 1000
        compression: "gzip"

    使用 'es-archive COMMAND --help' 查看具体命令的详细帮助。
    """
    # 设置上下文
    ctx.verbose = verbose
    ctx.quiet = quiet
    ctx.debug = debug
    ctx.config_file = config

    # 设置日志
    if quiet:
        log_level = 'ERROR'
    elif verbose or debug:
        log_level = 'DEBUG'

    log_config = {
        'level': log_level,
        'format': '%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d | %(message)s'
    }
    setup_logging(log_config)

    # 加载配置
    try:
        if config:
            ctx.config = reload_settings(config)
        else:
            ctx.config = get_settings()

        if verbose or debug:
            click.echo(f"配置文件: {config or '默认配置'}")
            click.echo(f"日志级别: {log_level}")
            if debug:
                click.echo("🐛 调试模式已启用 - 将显示所有ES操作的curl命令")

    except Exception as e:
        click.echo(f"错误: 加载配置失败: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument('index', required=True)
@click.argument('output_dir', type=click.Path(path_type=Path), required=True)
@click.option(
    '--strategy',
    type=click.Choice(['full', 'incremental', 'time_range']),
    default='full',
    show_default=True,
    help='备份策略: full=完整备份, incremental=增量备份, time_range=时间范围备份'
)
@click.option(
    '--time-field',
    help='时间字段名称 (time_range策略必需，如: @timestamp, created_at)'
)
@click.option(
    '--start-time',
    help='开始时间，ISO格式 (如: 2025-01-01T00:00:00Z)，包含此时间点'
)
@click.option(
    '--end-time',
    help='结束时间，ISO格式 (如: 2025-01-31T23:59:59Z)，包含此时间点'
)
@click.option(
    '--batch-size',
    type=int,
    default=1000,
    show_default=True,
    help='每批处理的文档数量 (建议: 500-5000)'
)
@click.option(
    '--compression',
    type=click.Choice(['gzip', 'lz4', 'none']),
    default='gzip',
    show_default=True,
    help='压缩算法: gzip=高压缩率, lz4=高速度, none=无压缩'
)
@click.option(
    '--parallel-workers',
    type=int,
    default=4,
    show_default=True,
    help='并行工作线程数 (建议: 2-8)'
)
@click.option(
    '--validate/--no-validate',
    default=True,
    show_default=True,
    help='是否验证备份完整性'
)
@click.option(
    '--delete-after-backup/--no-delete-after-backup',
    default=False,
    show_default=True,
    help='备份完成后是否删除已备份的数据 (谨慎使用！)'
)
@click.option(
    '--backup-name',
    help='自定义备份名称 (如不指定，默认使用: 索引名-时间戳)'
)
@pass_context
def backup(
    ctx: CLIContext,
    index: str,
    output_dir: Path,
    strategy: str,
    time_field: Optional[str],
    start_time: Optional[str],
    end_time: Optional[str],
    batch_size: int,
    compression: str,
    parallel_workers: int,
    validate: bool,
    delete_after_backup: bool,
    backup_name: Optional[str]
):
    """备份Elasticsearch索引数据

    \b
    必需参数:
      INDEX       要备份的索引名称 (支持通配符，如: logs-*, app-*)
      OUTPUT_DIR  备份文件输出目录路径

    \b
    备份策略说明:
      full        完整备份所有数据 (默认)
      incremental 增量备份 (需要上次备份的元数据)
      time_range  按时间范围备份 (需要指定时间字段和范围)

    \b
    示例:
      # 完整备份索引
      es-archive backup my-index /backup/dir

      # 按时间范围备份 (备份指定时间点及之前的数据)
      es-archive backup app-logs /backup/dir \\
        --end-time 2025-07-11T00:00:00Z

      # 备份指定时间范围内的数据 (包含起止时间点)
      es-archive backup app-logs /backup/dir \\
        --start-time 2025-07-01T00:00:00Z \\
        --end-time 2025-07-11T23:59:59Z

      # 高性能备份 (更多并发，LZ4压缩)
      es-archive backup large-index /backup/dir \\
        --batch-size 2000 \\
        --parallel-workers 8 \\
        --compression lz4

    \b
    环境变量:
      ES_HOST     Elasticsearch服务器地址 (默认: http://localhost:9200)
      ES_USERNAME Elasticsearch用户名
      ES_PASSWORD Elasticsearch密码
    """
    from .commands.backup import BackupCommand

    try:
        cmd = BackupCommand(ctx.config, debug_mode=ctx.debug)
        
        # 智能推断备份策略
        inferred_strategy = strategy
        if strategy == 'full' and (start_time or end_time):
            # 如果用户指定了时间参数但策略是full，自动推断为time_range
            inferred_strategy = 'time_range'
            click.echo("ℹ 检测到时间参数，自动使用 time_range 策略")

        # 构建备份参数
        backup_params = {
            'strategy_type': inferred_strategy,
            'batch_size': batch_size,
            'compression': compression,
            'parallel_workers': parallel_workers,
            'validate_data': validate,
            'delete_after_backup': delete_after_backup,
        }

        # 添加自定义备份名称
        if backup_name:
            backup_params['backup_name'] = backup_name
        
        if time_field:
            backup_params['time_field'] = time_field
        
        if start_time:
            from datetime import datetime, timezone, timedelta
            # 北京时区
            beijing_tz = timezone(timedelta(hours=8))
            parsed_start = datetime.fromisoformat(start_time)
            if parsed_start.tzinfo is None:
                # 如果没有时区信息，假设为北京时区
                parsed_start = parsed_start.replace(tzinfo=beijing_tz)
            backup_params['time_range_start'] = parsed_start

        if end_time:
            from datetime import datetime, timezone, timedelta
            # 北京时区
            beijing_tz = timezone(timedelta(hours=8))
            parsed_end = datetime.fromisoformat(end_time)
            if parsed_end.tzinfo is None:
                # 如果没有时区信息，假设为北京时区
                parsed_end = parsed_end.replace(tzinfo=beijing_tz)
            backup_params['time_range_end'] = parsed_end
        
        # 执行备份
        result = cmd.execute(index, str(output_dir), backup_params)
        
        if not ctx.quiet:
            click.echo(f"备份完成: {result.backup_id}")
            click.echo(f"备份路径: {result.backup_path}")
            click.echo(f"文档数量: {result.backed_up_documents}")
            click.echo(f"状态: {result.status}")
        
    except ESArchiveError as e:
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        logger.exception("备份过程中发生未知错误")
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument('backup_id', required=True)
@click.argument('target_index', required=True)
@click.option(
    '--strategy',
    type=click.Choice(['full', 'selective', 'time_range']),
    default='full',
    show_default=True,
    help='恢复策略: full=完整恢复, selective=选择性恢复, time_range=时间范围恢复'
)
@click.option(
    '--create-index/--no-create-index',
    default=True,
    show_default=True,
    help='是否自动创建目标索引 (包括映射和设置)'
)
@click.option(
    '--overwrite/--no-overwrite',
    default=False,
    show_default=True,
    help='是否覆盖现有索引数据 (危险操作!)'
)
@click.option(
    '--batch-size',
    type=int,
    default=500,
    show_default=True,
    help='每批恢复的文档数量 (建议: 100-1000，启用自适应时作为初始值)'
)
@click.option(
    '--parallel-workers',
    type=int,
    default=2,
    show_default=True,
    help='并行工作线程数 (恢复时建议较少以避免ES压力)'
)
@click.option(
    '--validate/--no-validate',
    default=True,
    show_default=True,
    help='是否验证恢复数据完整性'
)
@click.option(
    '--adaptive-batch/--no-adaptive-batch',
    default=True,
    show_default=True,
    help='是否启用自适应批量大小 (根据ES内存状况自动调整)'
)
@click.option(
    '--circuit-breaker-retry/--no-circuit-breaker-retry',
    default=True,
    show_default=True,
    help='是否在遇到断路器异常时自动重试'
)
@click.option(
    '--max-memory',
    type=str,
    default='500MB',
    show_default=True,
    help='单次批量操作最大内存使用量 (如: 500MB, 1GB)'
)
@click.option(
    '--max-retries',
    type=int,
    default=3,
    show_default=True,
    help='遇到错误时的最大重试次数'
)
@pass_context
def restore(
    ctx: CLIContext,
    backup_id: str,
    target_index: str,
    strategy: str,
    create_index: bool,
    overwrite: bool,
    batch_size: int,
    parallel_workers: int,
    validate: bool,
    adaptive_batch: bool,
    circuit_breaker_retry: bool,
    max_memory: str,
    max_retries: int
):
    """从备份文件恢复数据到Elasticsearch索引

    \b
    必需参数:
      BACKUP_ID     备份文件的唯一标识符 (可通过 list-backups 命令查看)
      TARGET_INDEX  目标索引名称 (可以与原索引不同)

    \b
    恢复策略说明:
      full       完整恢复所有备份数据 (默认)
      selective  选择性恢复指定条件的数据
      time_range 按时间范围恢复数据

    \b
    示例:
      # 基本恢复操作
      es-archive restore backup_20250722_001 restored-index

      # 恢复到新索引，覆盖现有数据
      es-archive restore backup_20250722_001 new-index \\
        --overwrite \\
        --create-index

      # 高速恢复 (适用于大数据量，禁用安全功能)
      es-archive restore backup_20250722_001 target-index \\
        --batch-size 1000 \\
        --parallel-workers 4 \\
        --no-validate \\
        --no-adaptive-batch

      # 安全恢复 (推荐，自动处理内存问题)
      es-archive restore backup_20250722_001 target-index \\
        --adaptive-batch \\
        --circuit-breaker-retry \\
        --max-memory 300MB \\
        --max-retries 5

    \b
    注意事项:
      - 使用 --overwrite 会删除目标索引的现有数据
      - 恢复前建议先备份目标索引
      - 启用自适应批量大小可自动处理ES内存限制问题
      - 遇到断路器异常时，系统会自动调整批量大小并重试
    """
    from .commands.restore import RestoreCommand

    try:
        cmd = RestoreCommand(ctx.config, debug_mode=ctx.debug)
        
        # 构建恢复参数
        restore_params = {
            'strategy_type': strategy,
            'target_index': target_index,
            'create_index': create_index,
            'overwrite_existing': overwrite,
            'batch_size': batch_size,
            'parallel_workers': parallel_workers,
            'validate_data': validate,
            'adaptive_batch_size': adaptive_batch,
            'circuit_breaker_retry': circuit_breaker_retry,
            'max_memory_usage': max_memory,
            'max_retries': max_retries,
        }
        
        # 执行恢复
        result = cmd.execute(backup_id, restore_params)
        
        if not ctx.quiet:
            click.echo(f"恢复完成: {result.restore_id}")
            click.echo(f"目标索引: {result.target_index}")
            click.echo(f"恢复文档: {result.restored_documents}")
            click.echo(f"跳过文档: {result.skipped_documents}")
            click.echo(f"状态: {result.status}")
        
    except ESArchiveError as e:
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        logger.exception("恢复过程中发生未知错误")
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.option(
    '--format',
    type=click.Choice(['table', 'json', 'yaml']),
    default='table',
    show_default=True,
    help='输出格式: table=表格, json=JSON格式, yaml=YAML格式'
)
@click.option(
    '--status',
    type=click.Choice(['running', 'completed', 'failed', 'cancelled']),
    help='按状态过滤备份列表'
)
@click.option(
    '--limit',
    type=int,
    default=20,
    show_default=True,
    help='限制显示的备份数量'
)
@pass_context
def list_backups(ctx: CLIContext, format: str, status: Optional[str], limit: int):
    """列出所有可用的备份文件

    \b
    显示信息:
      • 备份ID和创建时间
      • 源索引名称和文档数量
      • 备份状态和文件大小
      • 备份策略和压缩方式

    \b
    示例:
      # 显示所有备份 (表格格式)
      es-archive list-backups

      # 只显示已完成的备份
      es-archive list-backups --status completed

      # 以JSON格式输出前10个备份
      es-archive list-backups --format json --limit 10

      # 以YAML格式输出所有失败的备份
      es-archive list-backups --format yaml --status failed
    """
    from .commands.list import ListCommand
    
    try:
        cmd = ListCommand(ctx.config)
        backups = cmd.list_backups(status=status, limit=limit)
        
        if format == 'table':
            cmd.print_backups_table(backups)
        elif format == 'json':
            cmd.print_backups_json(backups)
        elif format == 'yaml':
            cmd.print_backups_yaml(backups)
        
    except ESArchiveError as e:
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        logger.exception("列出备份时发生未知错误")
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument('backup_id', required=True)
@click.option(
    '--detailed/--no-detailed',
    default=False,
    show_default=True,
    help='显示详细的验证信息和错误报告'
)
@pass_context
def validate(ctx: CLIContext, backup_id: str, detailed: bool):
    """验证备份文件的完整性和可用性

    \b
    必需参数:
      BACKUP_ID  要验证的备份文件ID (可通过 list-backups 查看)

    \b
    验证内容:
      • 备份文件存在性和可读性
      • 元数据文件完整性
      • 数据文件校验和验证
      • 压缩文件完整性检查

    \b
    示例:
      # 快速验证备份
      es-archive validate backup_20250722_001

      # 详细验证并显示所有检查项
      es-archive validate backup_20250722_001 --detailed

    \b
    退出代码:
      0  验证通过
      1  验证失败或发现错误
    """
    from .commands.validate import ValidateCommand
    
    try:
        cmd = ValidateCommand(ctx.config)
        result = cmd.validate_backup(backup_id, detailed=detailed)
        
        if result.valid:
            click.echo("✓ 备份验证通过", color='green')
        else:
            click.echo("✗ 备份验证失败", color='red')
        
        if detailed or not result.valid:
            cmd.print_validation_result(result)
        
        if not result.valid:
            sys.exit(1)
        
    except ESArchiveError as e:
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        logger.exception("验证过程中发生未知错误")
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument('index', required=True)
@click.option(
    '--time-field',
    help='指定要分析的时间字段名称 (如: @timestamp, created_at)'
)
@click.option(
    '--sample-size',
    type=int,
    default=1000,
    show_default=True,
    help='分析样本大小 (建议: 100-10000)'
)
@pass_context
def analyze(ctx: CLIContext, index: str, time_field: Optional[str], sample_size: int):
    """分析Elasticsearch索引结构和时间字段分布

    \b
    必需参数:
      INDEX  要分析的索引名称 (支持通配符，如: logs-*)

    \b
    分析内容:
      • 索引映射结构和字段类型
      • 时间字段检测和格式分析
      • 数据分布统计
      • 备份策略建议

    \b
    示例:
      # 分析单个索引
      es-archive analyze my-index

      # 分析指定时间字段
      es-archive analyze app-logs --time-field created_at

      # 使用更大样本进行详细分析
      es-archive analyze large-index --sample-size 5000

    \b
    输出信息:
      - 索引基本信息 (文档数量、大小等)
      - 字段映射和类型分布
      - 时间字段候选和格式
      - 推荐的备份参数
    """
    from .commands.analyze import AnalyzeCommand
    
    try:
        cmd = AnalyzeCommand(ctx.config)
        result = cmd.analyze_index(index, time_field=time_field, sample_size=sample_size)
        
        cmd.print_analysis_result(result)
        
    except ESArchiveError as e:
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        logger.exception("分析过程中发生未知错误")
        click.echo(f"错误: {e}", err=True)
        sys.exit(1)


def main():
    """主入口函数"""
    try:
        cli()
    except KeyboardInterrupt:
        click.echo("\n操作被用户中断", err=True)
        sys.exit(130)
    except Exception as e:
        logger.exception("程序发生未知错误")
        click.echo(f"致命错误: {e}", err=True)
        sys.exit(1)


if __name__ == '__main__':
    main()
